package com.leadingsoft.pwxk.config;

import javax.servlet.Filter;

import org.jasig.cas.client.validation.Cas20ServiceTicketValidator;
import org.jasig.cas.client.validation.TicketValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.cas.authentication.CasAssertionAuthenticationToken;
import org.springframework.security.cas.authentication.CasAuthenticationProvider;
import org.springframework.security.cas.web.CasAuthenticationFilter;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.session.HttpSessionEventPublisher;

import com.leadingsoft.plat.security.AdvancedAuthenticationProvider;
import com.leadingsoft.plat.security.CacheablePasswordEncoder;
import com.leadingsoft.plat.services.UserService;

@Configuration
@EnableWebSecurity
@EnableConfigurationProperties({ CasProperties.class })
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	private AuthenticationSuccessHandler successHandler;

	@Autowired
	private AuthenticationFailureHandler failureHandler;

	@Autowired
	private AuthenticationEntryPoint entryPoint;

	@Autowired
	private UserService userDetailsService;

	@Qualifier("logoutLogHandler")
	@Autowired
	private LogoutHandler logoutLogHandler;

	// @Autowired
	// private DxService dxService;

	@Autowired
	private CasProperties casProperties;

	// @Autowired
	// private PasswordEncoder passwordEncoder;

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.authorizeRequests()
				.antMatchers("/", "/index.html", "/favicon.ico", "/fonts/**", "/css/**", "/js/**", "/w/validateCode/**",
						"/w/template/list", "/h2**", "/h2/**", "#/casLogin")
				.permitAll().anyRequest().authenticated().and();
		http.formLogin().successHandler(successHandler).failureHandler(failureHandler).and().httpBasic().disable();
		http.logout().addLogoutHandler(logoutLogHandler).logoutSuccessHandler(logoutSuccessHandler());
		http.csrf().disable();
		http.exceptionHandling().authenticationEntryPoint(entryPoint);
		http.addFilter(casAuthenticationFilter());
		// http.addFilterBefore(interceptor(), FilterSecurityInterceptor.class);

		// session管理相关
		// maximumSessions设置每个用户最大会话数，
		// maxSessionsPreventsLogin:为true时会阻止新用户上线，false时会踢掉原来会话,建议设置为false,默认是false
		// 因为当用户直接关闭浏览器时，并不一定会发出登出请求，这就导致服务器的session不会失效。从而导致新的会话不能上线
		// expiredUrl：当用户session失效时，会发送302响应到指定的地址。我们需要处理相关的请求，如果是spa应该怎么
		// 当再次发送请求是，就会进入entrypoint进行登录过程了
		// http.sessionManagement().maximumSessions(1).maxSessionsPreventsLogin(false).expiredUrl("/login");
		http.sessionManagement().sessionFixation().changeSessionId();
	}

	@Bean
	public LogoutSuccessHandler logoutSuccessHandler() {
		return new HttpStatusReturningLogoutSuccessHandler();
	}

	private Filter casAuthenticationFilter() throws Exception {
		CasAuthenticationFilter filter = new CasAuthenticationFilter();
		filter.setAuthenticationManager(authenticationManager());
		filter.setAuthenticationSuccessHandler(successHandler);
		return filter;
	}

	/**
	 * 重新配置认证的provider,可以做一些别的事情。
	 */
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.authenticationProvider(authenticationProvider());
		auth.authenticationProvider(casAuthenticationProvider());
	}

	/**
	 * 重新配置一个provider，使之可以记录登录错误信息
	 * 
	 * @return
	 */
	@Bean
	public AdvancedAuthenticationProvider authenticationProvider() {
		AdvancedAuthenticationProvider provider = new AdvancedAuthenticationProvider();
		provider.setUserDetailsService(userDetailsService);
		provider.setPasswordEncoder(passwordEncoder());
		provider.setMaxError(5);
		provider.setMaxLockMilliseconds(1000 * 60 * 60 * 2);
		return provider;
	}

	/** 这些代码为cas配置 **/
	@Bean
	public AuthenticationProvider casAuthenticationProvider() {
		CasAuthenticationProvider provider = new CasAuthenticationProvider();
		provider.setAuthenticationUserDetailsService(authenticationUserDetailsService());
		provider.setServiceProperties(serviceProperties());
		provider.setTicketValidator(ticketValidator());
		provider.setKey("sjz2_cas_key");
		return provider;
	}

	@Bean
	public TicketValidator ticketValidator() {
		return new Cas20ServiceTicketValidator(casProperties.getTicketValidator());
	}

	@Bean
	public ServiceProperties serviceProperties() {
		ServiceProperties properties = new ServiceProperties();
		properties.setSendRenew(false);
		properties.setService(casProperties.getServiceUrl());
		return properties;
	}

	@Bean
	public AuthenticationUserDetailsService<CasAssertionAuthenticationToken> authenticationUserDetailsService() {
		return new AuthenticationUserDetailsService<CasAssertionAuthenticationToken>() {
			@Override
			public UserDetails loadUserDetails(CasAssertionAuthenticationToken token) throws UsernameNotFoundException {
				String email = token.getName();
				return userDetailsService.loadUserByEmail(email);
			}
		};
	}

	/** CAS配置代码终止 **/
	/**
	 * 必须添加这个bean， 这个listener会在用户登出时，通知容器某个session已经失效，这时，用户可以在别的地方上线
	 * 
	 * @return
	 */
	@Bean
	public ServletListenerRegistrationBean<HttpSessionEventPublisher> httpSessionEventPublisher() {
		return new ServletListenerRegistrationBean<HttpSessionEventPublisher>(new HttpSessionEventPublisher());
	}

	@Bean
	public PasswordEncoder passwordEncoder() {
		return new CacheablePasswordEncoder(new BCryptPasswordEncoder());
	}
}
